package cn.kinoko.service.impl;

import cn.kinoko.config.ConfigProperties;
import cn.kinoko.model.RPType;
import cn.kinoko.service.RedisService;
import cn.kinoko.utils.TypeChecker;
import lombok.Getter;
import org.apache.commons.beanutils.PropertyUtils;
import org.redisson.api.*;
import org.redisson.client.protocol.ScoredEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.*;

/**
 * redis api服务端实现类
 *
 * @author kinoko
 */
@Getter
@Service
public class RedisServiceImpl implements RedisService {

    @Value("${spring.application.name:none}")
    private String appCode;
    @Value("${spring.profiles.active:none}")
    private String profile;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ConfigProperties configProperties;


    /**
     * 拼接key前缀，用于应用分区
     *
     * @param key key
     * @return 拼接后的key
     */
    private String wrap(String key) {
        if (!configProperties.isEnablePrefix() || "none".equals(appCode) || "none".equals(profile)) {
            return key;
        }
        StringBuilder sb = new StringBuilder(appCode).append(":").append(profile).append(":").append(key);
        if (!key.endsWith(":")) {
            sb.append(":");
        }
        return sb.toString();
    }

    /**
     * 设置过期时间
     *
     * @param expire     过期对象
     * @param expireTime 过期时间
     */
    private void setExpire(RExpirable expire, Duration expireTime) {
        if (expire != null && expireTime.compareTo(Duration.ZERO) > 0) {
            expire.expire(expireTime);
        }
    }

    /**
     * 获取所有匹配的key
     * command: keys pattern
     *
     * @param pattern 匹配的key
     * @return 匹配的key
     */
    @Override
    public Iterable<String> getKeys(String pattern) {
        return redissonClient.getKeys().getKeysByPattern(pattern);
    }

    /**
     * 获取缓存类型
     * 指令：type key
     *
     * @param key key
     * @return 缓存类型
     */
    @Override
    public Optional<?> matchTypeAndGet(String key) {
        String type = redissonClient.getScript().eval(RScript.Mode.READ_ONLY,
                "return redis.call('type', KEYS[1])",
                RScript.ReturnType.VALUE,
                Collections.singletonList(wrap(key)));
        switch (RPType.get(type)) {
            case STR: return Optional.of(get(key));
            case SET: return Optional.of(getSet(key));
            case HASH: return Optional.of(getMap(key));
            case LIST: return Optional.of(getList(key));
            case ZSET: return Optional.of(getScoredSortedSet(key));
            default: return Optional.empty();
        }
    }

    /**
     * 匹配类型并设置缓存
     * command: type key
     *
     * @param key key
     */
    @Override
    public <V> void matchTypeAndSet(String key, V value, Duration expire) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        // 判断是否为list
        if (value instanceof Collection) {
            putList(key, (Collection<?>) value, expire);
            return;
        }
        // 判断是否为map
        if (value instanceof Map) {
            putMap(key, (Map<?, ?>) value, expire);
            return;
        }
        // 判断是否为基本类型或包装类
        if (TypeChecker.isPrimitiveOrWrapper(value)) {
            putStr(key, value, expire);
            return;
        }
        // 对象转map
        Map<String, Object> map = PropertyUtils.describe(value);
        putMap(key, map, expire);
    }

    /**
     * 设置list缓存
     *
     * @param redisKey redisKey
     * @param target   target
     * @param expire   expire
     */
    private void putList(String redisKey, Collection<?> target, Duration expire) {
        RList<Object> list;
        if (expire != null) {
            list = getList(redisKey, expire);
        } else {
            list = getList(redisKey);
        }
        list.addAll(target);
    }

    /**
     * 设置字符串缓存
     *
     * @param redisKey redisKey
     * @param target   target
     * @param expire   expire
     */
    private void putStr(String redisKey, Object target, Duration expire) {
        if (expire != null) {
            set(redisKey, target, expire);
        } else {
            set(redisKey, target);
        }
    }

    /**
     * 设置map缓存
     *
     * @param redisKey redisKey
     * @param target   target
     * @param expire   expire
     */
    private void putMap(String redisKey, Map<?, ?> target, Duration expire) {
        RMap<Object, Object> map;
        if (expire != null) {
            map = getMap(redisKey, expire);
        } else {
            map = getMap(redisKey);
        }
        map.putAll(target);
    }

    /**
     * 设置缓存
     * 指令：set key value
     *
     * @param key   key
     * @param value value
     */
    @Override
    public <V> void set(String key, V value) {
        redissonClient.getBucket(wrap(key)).set(value);
    }

    /**
     * 设置缓存
     * command: set key value [EX seconds] [PX milliseconds] [NX|XX]
     *
     * @param key   key
     * @param value value
     */
    @Override
    public void set(String key, Object value, Duration expire) {
        redissonClient.getBucket(wrap(key)).set(value, expire);
    }

    /**
     * 批量设置多个缓存
     * command: mset key value [key value …]
     *
     * @param temps temps
     */
    @Override
    public <V> void multiSet(Map<String, V> temps) {
        redissonClient.getBuckets().set(temps);
    }

    /**
     * 获取缓存
     * command: get key
     *
     * @param key key
     * @return value
     */
    @Override
    public <T> T get(String key) {
        RBucket<T> bucket = redissonClient.getBucket(wrap(key));
        return bucket.get();
    }

    /**
     * 获取所有匹配 pattern 的缓存key 对应的缓存值
     * 此方法 谨慎使用
     *
     * @param pattern pattern
     * @return 匹配的缓存值
     */
    @Override
    public <V> Set<V> getAll(String pattern) {
        try {
            Set<V> set = new HashSet<>();
            Iterable<String> keys = redissonClient.getKeys().getKeysByPattern(pattern);
            for (String key : keys) {
                RBucket<V> bucket = redissonClient.getBucket(key);
                set.add(bucket.get());
            }
            return set;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取缓存过期时间
     * command: ttl key
     *
     * @param key key
     * @return 剩余时间
     */
    @Override
    public Long ttl(String key) {
        return redissonClient.getBucket(wrap(key)).remainTimeToLive();
    }

    /**
     * 删除缓存
     * command: del key
     *
     * @param key key
     * @return 是否删除成功
     */
    @Override
    public boolean del(String key) {
        return redissonClient.getScript().eval(RScript.Mode.READ_WRITE,
                "return redis.call('del', KEYS[1])",
                RScript.ReturnType.BOOLEAN,
                Collections.singletonList(wrap(key)));
    }

    /**
     * 删除所有匹配 pattern 的缓存key
     * command: del key
     *
     * @param pattern pattern
     */
    @Override
    public void delAll(String pattern) {
        redissonClient.getKeys().deleteByPattern(pattern);
    }

    /**
     * 获取Map
     *
     * @param key        key
     * @param expireTime 过期时间
     * @return Map
     */
    @Override
    public <K, V> RMap<K, V> getMap(String key, Duration expireTime) {
        RMap<K, V> map = redissonClient.getMap(wrap(key));
        if (expireTime != null) {
            setExpire(map, expireTime);
        }
        return redissonClient.getMap(key);
    }

    /**
     * 获取Map
     * command: hset key
     *
     * @param key key
     * @return Map
     */
    @Override
    public <K, V> RMap<K, V> getMap(String key) {
        return getMap(wrap(key), null);
    }

    /**
     * Map中是否存在field
     * command: hexists key field
     *
     * @param key   key
     * @param field field
     * @return 是否存在
     */
    @Override
    public <K> boolean hasMapKey(String key, K field) {
        return redissonClient.getMap(wrap(key)).containsKey(field);
    }

    /**
     * 获取Map中的所有field
     * command: hkeys key
     *
     * @param key key
     * @return field集合
     */
    @Override
    public Set<Object> getMapKeyList(String key) {
        return redissonClient.getMap(wrap(key)).readAllKeySet();
    }

    /**
     * 获取Map中的所有value
     * command: hvals key
     *
     * @param key key
     * @return value集合
     */
    @Override
    public List<Object> getMapValueList(String key) {
        return new ArrayList<>(redissonClient.getMap(wrap(key)).readAllValues());
    }

    /**
     * 设置Map中的field
     * command: hset key field value
     *
     * @param key   key
     * @param field field
     * @param value value
     */
    @Override
    public <K, V> void putMapEntry(String key, K field, V value) {
        getMap(key).put(field, value);
    }

    /**
     * 设置Map中的field
     * command: hset key field value [ex seconds] [px milliseconds]
     *
     * @param key        key
     * @param field      field
     * @param value      value
     * @param expireTime 过期时间
     */
    @Override
    public <K, V> void putMapEntry(String key, K field, V value, Duration expireTime) {
        getMap(key, expireTime).put(field, value);
    }

    /**
     * 获取Map中的field
     * command: hget key field
     *
     * @param key   key
     * @param field field
     * @return value
     */
    @Override
    public <K, V> V getMapValue(String key, K field) {
        RMap<K, V> map = getMap(key);
        return map.get(field);
    }

    /**
     * 删除Map中的field
     * command: hdel key field
     *
     * @param key   key
     * @param field field
     */
    @Override
    public <K> void delValue(String key, K field) {
        getMap(key).remove(field);
    }

    /**
     * 获取List
     * command: lset key
     *
     * @param key        key
     * @param expireTime 过期时间
     * @return List
     */
    @Override
    public <V> RList<V> getList(String key, Duration expireTime) {
        RList<V> list = redissonClient.getList(wrap(key));
        if (expireTime != null) {
            setExpire(list, expireTime);
        }
        return redissonClient.getList(wrap(key));
    }

    /**
     * 获取List
     * command: lset key
     *
     * @param key key
     * @return List
     */
    @Override
    public <V> RList<V> getList(String key) {
        return getList(key, null);
    }

    /**
     * 删除List元素
     * command: lrem key count value
     *
     * @param key     key
     * @param element 元素
     * @return 是否存在
     */
    @Override
    public <V> boolean delListElement(String key, V element) {
        return getList(key).remove(element);
    }

    /**
     * 获取Set
     * command: sset key
     *
     * @param key        key
     * @param expireTime 过期时间
     * @return Set
     */
    @Override
    public <T> RSet<T> getSet(String key, Duration expireTime) {
        RSet<T> set = redissonClient.getSet(wrap(key));
        if (expireTime != null) {
            setExpire(set, expireTime);
        }
        return set;
    }

    /**
     * 获取Set
     * command: sset key
     *
     * @param key key
     * @return Set
     */
    @Override
    public <T> RSet<T> getSet(String key) {
        return getSet(key, null);
    }

    /**
     * 是否有Set元素
     * command: sismember key value
     *
     * @param key     key
     * @param element 元素
     * @return 是否存在
     */
    @Override
    public <V> boolean hasSetElement(String key, V element) {
        return getSet(key).contains(element);
    }

    /**
     * 删除Set 元素
     * command: srem key value
     *
     * @param key     key
     * @param element 元素
     * @return 是否删除成功
     */
    @Override
    public <V> boolean delSetElement(String key, V element) {
        return getSet(key).remove(element);
    }

    /**
     * 获取Zset
     *
     * @param key        key
     * @param expireTime 过期时间
     * @return 有序集合
     */
    @Override
    public <V> RScoredSortedSet<V> getScoredSortedSet(String key, Duration expireTime) {
        RScoredSortedSet<V> zset = redissonClient.getScoredSortedSet(wrap(key));
        if (expireTime != null) {
            setExpire(zset, expireTime);
        }
        return zset;
    }

    /**
     * 获取Zset
     * command: zset key
     *
     * @param key key
     * @return 有序集合
     */
    @Override
    public <V> RScoredSortedSet<V> getScoredSortedSet(String key) {
        return getScoredSortedSet(key, null);
    }

    /**
     * 分数排序集合添加元素
     * 默认从低分到高分排序
     * 默认分数为0
     * command: zadd key score value
     *
     * @param key     key
     * @param element 元素
     */
    @Override
    public <V> void addScoredSortedSetElement(String key, V element) {
        getScoredSortedSet(key).add(0, element);
    }

    /**
     * 分数排序集合添加元素
     * 默认从低分到高分排序
     * 指定分数
     * command: zadd key score value
     *
     * @param key     key
     * @param element 元素
     * @param score   分数
     */
    @Override
    public <V> void addScoredSortedSetElement(String key, V element, double score) {
        getScoredSortedSet(key).add(score, element);
    }

    /**
     * 获取分数排序集合
     * 以相反的顺序按排名范围返回值
     * command: zrevrange key start stop
     *
     * @param key        key
     * @param startIndex 开始下标
     * @param endIndex   结束下标
     * @return 元素集合
     */
    @Override
    public <V> Collection<V> getScoredSortedSetValueRangeReversed(String key, int startIndex, int endIndex) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.valueRangeReversed(startIndex, endIndex);
    }

    /**
     * 获取分数排序集合
     * 顺序按排名范围返回值
     * command: zrange key start stop
     *
     * @param key        key
     * @param startIndex 开始下标
     * @param endIndex   结束下标
     * @return 元素集合
     */
    @Override
    public <V> Collection<V> getScoredSortedSetValueRange(String key, int startIndex, int endIndex) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.valueRange(startIndex, endIndex);
    }

    /**
     * 获取分数排序集合
     * 以相反的顺序按排名范围返回值
     * command: zrevrange key 0 -1
     *
     * @param key key
     * @return 元素集合
     */
    @Override
    public <V> Collection<V> getScoredSortedSetValueReversed(String key) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.valueRangeReversed(0, -1);
    }

    /**
     * 获取分数排序集合
     * 顺序按排名范围返回值
     * command: zrange key 0 -1
     *
     * @param key key
     * @return 元素集合
     */
    @Override
    public <V> Collection<V> getScoredSortedSetValue(String key) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.valueRange(0, -1);
    }

    /**
     * 获取分数排序集合
     * 以相反的顺序按排名范围返回条目（值及其分数）
     * command: zrevrange key start stop withscores
     *
     * @param key        key
     * @param startIndex 开始下标
     * @param endIndex   结束下标
     * @return 元素集合
     */
    @Override
    public <V> Collection<ScoredEntry<V>> getScoredSortedSetEntryRangeReversed(String key, int startIndex, int endIndex) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.entryRangeReversed(startIndex, endIndex);
    }

    /**
     * 获取分数排序集合
     * 顺序按排名范围返回条目（值及其分数）
     * command: zrange key start stop withscores
     *
     * @param key        key
     * @param startIndex 开始下标
     * @param endIndex   结束下标
     * @return 元素集合
     */
    @Override
    public <V> Collection<ScoredEntry<V>> getScoredSortedSetEntryRange(String key, int startIndex, int endIndex) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.entryRange(startIndex, endIndex);
    }

    /**
     * 获取分数排序集合
     * 以相反的顺序按排名范围返回条目（值及其分数）
     * command: zrevrange key 0 -1 withscores
     *
     * @param key key
     * @return 元素集合
     */
    @Override
    public <V> Collection<ScoredEntry<V>> getScoredSortedSetEntryReversed(String key) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.entryRangeReversed(0, -1);
    }

    /**
     * 获取分数排序集合
     * 顺序按排名范围返回条目（值及其分数）
     * command: zrange key 0 -1 withscores
     *
     * @param key key
     * @return 元素集合
     */
    @Override
    public <V> Collection<ScoredEntry<V>> getScoredSortedSetEntry(String key) {
        RScoredSortedSet<V> scoredSortedSet = getScoredSortedSet(key);
        return scoredSortedSet.entryRange(0, -1);
    }

    /**
     * 获取元素分数
     * command: zscore key member
     *
     * @param key     key
     * @param element 元素
     * @return 分数
     */
    @Override
    public <V> Double getElementScore(String key, V element) {
        return getScoredSortedSet(key).getScore(element);
    }

    /**
     * 是否存在该元素
     * command: zscore key member
     *
     * @param key     key
     * @param element 元素
     * @return 是否存在
     */
    @Override
    public <V> boolean hasScoredSortedSetElement(String key, V element) {
        return getScoredSortedSet(key).contains(element);
    }

    /**
     * 删除指定元素
     * command: zrem key member
     *
     * @param key     key
     * @param element 元素
     * @return 是否删除成功
     */
    @Override
    public <V> boolean delScoredSortedSetElement(String key, V element) {
        return getScoredSortedSet(key).remove(element);
    }

    /**
     * 获取元素排名（从0开始计数）
     * command: zrank key member
     *
     * @param key     key
     * @param element 元素
     * @return 排名
     */
    @Override
    public <V> int getRankFormScoredSortedSet(String key, V element) {
        return getScoredSortedSet(key).rank(element);
    }

    /**
     * 获取元素倒排名（从0开始计数）
     * command: zrevrank key member
     *
     * @param key     key
     * @param element 元素
     * @return 排名
     */
    @Override
    public <V> int getReversedRankFormScoredSortedSet(String key, V element) {
        return getScoredSortedSet(key).revRank(element);
    }

    /**
     * 增加元素分数
     * 当分数为负数时，减少分数
     * command: zincrby key score member
     *
     * @param key     key
     * @param element 元素
     * @param score   分数
     */
    @Override
    public <V> void incrementElementScore(String key, V element, int score) {
        getScoredSortedSet(key).addScore(element, score);
    }

    /**
     * 获取分布式锁
     *
     * @param key key
     * @return 锁
     */
    @Override
    public RLock getLock(String key) {
        return redissonClient.getLock(key);
    }

    /**
     * 获取分布式限流器
     *
     * @param key key
     * @return 限流器
     */
    @Override
    public RRateLimiter getRateLimiter(String key) {
        return redissonClient.getRateLimiter(key);
    }

    /**
     * 获取脚本对象
     *
     * @return 脚本对象
     */
    @Override
    public RScript getLuaScript() {
        return redissonClient.getScript();
    }

    /**
     * 获取布隆过滤器
     *
     * @param redisKey redisKey
     * @return bloomFilter
     */
    @Override
    public <T> RBloomFilter<T> getBloomFilter(String redisKey) {
        return redissonClient.getBloomFilter(wrap(redisKey));
    }
}
